package guitplugin;

import com.guit.client.apt.GwtController;
import com.guit.client.apt.GwtDisplay;
import com.guit.client.apt.GwtPresenter;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Filer;
import com.sun.mirror.apt.Messager;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.AnnotationTypeElementDeclaration;
import com.sun.mirror.declaration.AnnotationValue;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.FieldDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.declaration.TypeParameterDeclaration;
import com.sun.mirror.type.ClassType;
import com.sun.mirror.type.InterfaceType;
import com.sun.mirror.type.PrimitiveType;
import com.sun.mirror.type.PrimitiveType.Kind;
import com.sun.mirror.type.TypeMirror;
import com.sun.mirror.type.VoidType;
import com.sun.mirror.util.DeclarationFilter;
import com.sun.mirror.util.Types;

import org.xml.sax.SAXParseException;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import guitplugin.guitview.GuitViewHelper;

public class GuitPresenterProcessor implements AnnotationProcessor {

  private final Messager messager;

  private String[] root;

  String domPackage = "com.google.gwt.event.dom.client.";
  String sharedPackage = "com.google.gwt.event.logical.shared.";

  private static final String containerError =
      "A gwt container method can only have 1 parameter of a type IsWidget";

  AnnotationProcessorEnvironment env;
  Types typeUtils;
  Filer filer;

  public GuitPresenterProcessor(AnnotationProcessorEnvironment env) {
    this.env = env;
    messager = env.getMessager();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
  }

  private void collectGetters(HashMap<String, MethodDeclaration> validFields,
      TypeDeclaration eventType) {
    if (eventType == null) {
      return;
    }

    Collection<? extends MethodDeclaration> classMethods = eventType.getMethods();
    for (MethodDeclaration methodDeclaration : classMethods) {
      String name = methodDeclaration.getSimpleName();
      if (!(methodDeclaration.getReturnType() instanceof VoidType)) {
        try {
          if (typeUtils.getPrimitiveType(Kind.BOOLEAN).equals(methodDeclaration.getReturnType())) {
            if (name.startsWith("is")) {
              name = name.substring(2);
              name = name.substring(0, 1).toLowerCase() + name.substring(1);
              validFields.put(name, methodDeclaration);
            }
          } else if (name.startsWith("get")) {
            name = name.substring(3);
            name = name.substring(0, 1).toLowerCase() + name.substring(1);
            validFields.put(name, methodDeclaration);
          }
        } catch (Exception e) {
          e.printStackTrace();
          // CONTINUE ON ANY PARSING ERRORS
        }
      }
    }

    Collection<InterfaceType> superinterfaces = eventType.getSuperinterfaces();
    for (InterfaceType interfaceType : superinterfaces) {
      collectGetters(validFields, interfaceType.getDeclaration());
    }

    if (eventType instanceof ClassDeclaration) {
      ClassType superclass = ((ClassDeclaration) eventType).getSuperclass();
      if (superclass != null) {
        collectGetters(validFields, superclass.getDeclaration());
      }
    }
  }

  protected String eventClassNameToEventName(String simpleName) {
    String eventName;
    eventName = simpleName.substring(0, simpleName.length() - 5);
    eventName = eventName.substring(0, 1).toLowerCase() + eventName.substring(1);
    return eventName;
  }

  protected String eventNameToEventClassName(String eventName) {
    eventName = eventName.substring(0, 1).toUpperCase() + eventName.substring(1) + "Event";
    return eventName;
  }

  private String fieldsToString(Set<String> names) {
    StringBuilder sb = new StringBuilder();
    for (String f : names) {
      if (sb.length() > 0) {
        sb.append(", ");
      }

      sb.append(f);
    }
    return sb.toString();
  }

  public AnnotationProcessorEnvironment getEnvironment() {
    return env;
  }

  private HashMap<String, MethodDeclaration> getValidFields(TypeDeclaration eventType) {
    HashMap<String, MethodDeclaration> validFields = new HashMap<String, MethodDeclaration>();
    collectGetters(validFields, eventType);
    return validFields;
  }

  @Override
  public void process() {
    root = env.getOptions().get("-sourcepath").split("[:]");

    for (String ann : GuitPresenterProcessorFactory.annotations) {
      for (ClassDeclaration d : DeclarationFilter.getFilter(ClassDeclaration.class)
          .filter(
              env.getDeclarationsAnnotatedWith((AnnotationTypeDeclaration) env
                  .getTypeDeclaration(ann)), ClassDeclaration.class)) {
        try {
          generateBinder(d);

          if (ann.equals(GwtController.class.getCanonicalName())) {
            generateControllerSuper(d);
            processController(d);
          } else if (ann.equals(GwtPresenter.class.getCanonicalName())) {
            if (!isWidget(d)) {
              generatePresenterSuper(d);
              processController(d);
              processPresenter(d);
            } else {
              generateWidgetSuper(d);
              processController(d);
              processPresenter(d);
            }
          } else {
            if (d.getSimpleName().endsWith("Controller")) {
              generateControllerSuper(d);
              processController(d);
              continue;
            }

            processController(d);
            generatePresenterSuper(d);
            processPresenter(d);
          }

          // Containers
          for (MethodDeclaration m : d.getMethods()) {
            if (m.getAnnotation(GwtDisplay.class) != null) {
              Collection<ParameterDeclaration> parameters = m.getParameters();
              if (parameters.size() != 1) {
                messager.printError(m.getPosition(), containerError);
              }
              ParameterDeclaration parameter = parameters.iterator().next();
              if (!((TypeDeclaration) parameter.getType()).getQualifiedName().equals(
                  "com.google.gwt.user.client.ui.IsWidget")) {
                messager.printError(parameter.getPosition(), containerError);
              }

              processContainerMethod(d, m);
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }

  private boolean isWidget(ClassDeclaration d) {
    Collection<AnnotationMirror> annotations = d.getAnnotationMirrors();
    for (AnnotationMirror a : annotations) {
      AnnotationTypeDeclaration decl = a.getAnnotationType().getDeclaration();
      if (decl == null) {
        continue;
      }

      String qualifiedName = decl.getQualifiedName();
      if (qualifiedName.equals("com.guit.client.apt.GwtPresenter")) {
        for (Entry<AnnotationTypeElementDeclaration, AnnotationValue> e : a.getElementValues()
            .entrySet()) {
          if (e.getKey().getSimpleName().equals("isWidget")) {
            return (Boolean) e.getValue().getValue();
          }
        }
      }
    }
    return false;
  }

  private void generateBinder(ClassDeclaration classDeclaration) {
    String packageName = classDeclaration.getPackage().getQualifiedName();
    String simpleName = classDeclaration.getSimpleName();
    String name = simpleName + "Binder";
    String qualifiedName = packageName + "." + name;
    PrintWriter writer;
    try {
      writer = filer.createSourceFile(qualifiedName);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    writer.println("package " + packageName + ";");
    writer.println();
    Generated.printGeneratedImport(writer);
    writer.println("import com.guit.client.binder.GuitBinder;");
    writer.println();

    // Suppress warnings on generic types
    Collection<TypeParameterDeclaration> parameters = classDeclaration.getFormalTypeParameters();
    if (parameters != null && parameters.size() > 0) {
      writer.println("@SuppressWarnings(\"rawtypes\")");
    }

    Generated.printGenerated(writer, simpleName);
    writer.println("public interface " + name + " extends GuitBinder<" + simpleName + "> {");
    writer.println("}");
    writer.close();
  }

  private void generatePresenterSuper(ClassDeclaration classDeclaration) {
    String packageName = classDeclaration.getPackage().getQualifiedName();
    String simpleName = classDeclaration.getSimpleName();
    String name = simpleName + "Presenter";
    String qualifiedName = packageName + "." + name;
    PrintWriter writer;
    try {
      writer = filer.createSourceFile(qualifiedName);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    writer.println("package " + packageName + ";");
    writer.println();
    Generated.printGeneratedImport(writer);
    writer.println("import com.guit.client.GuitPresenter;");
    writer.println();

    Generated.printGenerated(writer, simpleName);
    writer.println("public abstract class " + name + " extends GuitPresenter<" + simpleName
        + "Binder> {");

    writer.println("}");
    writer.close();
  }

  private void generateWidgetSuper(ClassDeclaration classDeclaration) {
    String packageName = classDeclaration.getPackage().getQualifiedName();
    String simpleName = classDeclaration.getSimpleName();
    String name = simpleName + "Widget";
    String qualifiedName = packageName + "." + name;
    PrintWriter writer;
    try {
      writer = filer.createSourceFile(qualifiedName);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    writer.println("package " + packageName + ";");
    writer.println();
    writer.println("import com.google.gwt.core.client.GWT;");
    writer.println();
    writer.println("import com.guit.client.GuitWidget;");
    Generated.printGeneratedImport(writer);
    writer.println();

    String binderName = simpleName + "Binder";
    Generated.printGenerated(writer, simpleName);
    writer.println("public abstract class " + name + " extends GuitWidget<" + binderName + "> {");

    writer.println("    public " + name + "() {");
    writer.println("            super((" + binderName + ") GWT.create(" + binderName + ".class));");
    writer.println("    }");

    writer.println("}");
    writer.close();
  }

  private void generateControllerSuper(ClassDeclaration classDeclaration) {
    String packageName = classDeclaration.getPackage().getQualifiedName();
    String simpleName = classDeclaration.getSimpleName();
    String name = simpleName + "Controller";
    String qualifiedName = packageName + "." + name;
    PrintWriter writer;
    try {
      writer = filer.createSourceFile(qualifiedName);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    writer.println("package " + packageName + ";");
    writer.println();
    writer.println("import com.guit.client.GuitController;");
    Generated.printGeneratedImport(writer);
    writer.println();

    String binderName = simpleName + "Binder";
    Generated.printGenerated(writer, simpleName);
    writer.println("public abstract class " + name + " extends GuitController<" + binderName
        + "> {");
    writer.println("}");
    writer.close();
  }

  @SuppressWarnings("unchecked")
  private void processPresenter(ClassDeclaration classDeclaration) {
    Set<String> names = null;

    try {
      names = GuitViewHelper.findUiFields(root, classDeclaration, true);
    } catch (SAXParseException e) {
      messager.printError(classDeclaration.getPosition(), String.format("Error parsing XML (line "
          + e.getLineNumber() + "): " + e.getMessage()));
      return;
    } catch (Exception e) {
      messager.printError(classDeclaration.getPosition(), e.toString());
      return;
    }

    // Validate autofocus and gwt editor
    Collection<AnnotationMirror> annotations = classDeclaration.getAnnotationMirrors();
    for (AnnotationMirror a : annotations) {
      AnnotationTypeDeclaration decl = a.getAnnotationType().getDeclaration();
      if (decl == null) {
        continue;
      }

      String qualifiedName = decl.getQualifiedName();
      if (qualifiedName.equals("com.guit.client.apt.GwtPresenter")) {
        for (Entry<AnnotationTypeElementDeclaration, AnnotationValue> e : a.getElementValues()
            .entrySet()) {
          if (e.getKey().getSimpleName().equals("autofocus")) {
            String field = (String) e.getValue().getValue();
            if (!names.contains(field)) {
              messager.printError(e.getValue().getPosition(), "The field '" + field
                  + "' is not declared in the view. Valid fields: " + fieldsToString(names));
            }
          }
        }
      } else if (qualifiedName.equals("com.guit.client.binder.GwtEditor")) {
        boolean hasDriver = false;
        for (FieldDeclaration f : classDeclaration.getFields()) {
          if (f.getSimpleName().equals("driver")) {
            hasDriver = true;
            break;
          }
        }
        if (!hasDriver) {
          messager
              .printError(
                  a.getPosition(),
                  "This class does not have a 'driver' field. i.e SimpleBeanEditorDriver<Person, ?> driver; or RequestFactoryEditorDriver<Person, ?> driver;");
        }
      }
    }

    {
      Collection<FieldDeclaration> fields = classDeclaration.getFields();
      for (FieldDeclaration f : fields) {

        Collection<AnnotationMirror> annotationsMirror = f.getAnnotationMirrors();

        for (AnnotationMirror mirror : annotationsMirror) {
          AnnotationTypeDeclaration declaration = mirror.getAnnotationType().getDeclaration();

          if (declaration == null) {
            continue;
          }

          // ViewField
          if (declaration.getQualifiedName().equals("com.guit.client.binder.ViewField")) {

            String fieldName = null;
            AnnotationValue annotationValueDeclaration = null;
            Boolean provided = false;
            Map<AnnotationTypeElementDeclaration, AnnotationValue> map = mirror.getElementValues();
            for (Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : map.entrySet()) {
              if (entry.getKey().getSimpleName().equals("name")) {
                annotationValueDeclaration = entry.getValue();
                fieldName = (String) entry.getValue().getValue();
              }

              if (entry.getKey().getSimpleName().equals("provided")) {
                provided = (Boolean) entry.getValue().getValue();
              }
            }

            GuitTypeVisitor visitor = new GuitTypeVisitor();
            f.getType().accept(visitor);
            if (!provided && visitor.isClass()) {
              messager.printError(f.getPosition(), "The type of a @ViewField must be an interface");
            }

            if (fieldName == null) {
              fieldName = f.getSimpleName();
            }

            if (!names.contains(fieldName)) {
              messager.printError(annotationValueDeclaration != null ? annotationValueDeclaration
                  .getPosition() : f.getPosition(), "The field '" + fieldName
                  + "' is not declared in the view. Valid fields: " + fieldsToString(names));
            }

            break;
          }
        }
      }
    }

    Collection<MethodDeclaration> methods = classDeclaration.getMethods();
    for (MethodDeclaration m : methods) {

      Collection<AnnotationMirror> annotationsMirror = m.getAnnotationMirrors();

      boolean isViewHandler = false;
      boolean hasViewFields = false;
      TypeDeclaration eventType = null;
      AnnotationMirror viewHandlerDeclaration = null;
      AnnotationTypeElementDeclaration eventTypeDeclaration = null;
      for (AnnotationMirror mirror : annotationsMirror) {
        AnnotationTypeDeclaration declaration = mirror.getAnnotationType().getDeclaration();

        // ViewHandler
        if (declaration.getQualifiedName().equals("com.guit.client.binder.ViewHandler")) {
          isViewHandler = true;
          viewHandlerDeclaration = mirror;
          Map<AnnotationTypeElementDeclaration, AnnotationValue> map = mirror.getElementValues();
          for (Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : map.entrySet()) {
            String simpleName = entry.getKey().getSimpleName();
            if (simpleName.equals("event")) {
              eventTypeDeclaration = entry.getKey();
              eventType = ((ClassType) entry.getValue().getValue()).getDeclaration();
            } else if (simpleName.equals("fields")) {
              hasViewFields = true;
              List<AnnotationValue> viewFields =
                  (List<AnnotationValue>) entry.getValue().getValue();

              for (AnnotationValue value : viewFields) {
                String field = (String) value.getValue();
                if (!names.contains(field)) {
                  messager.printError(value.getPosition(), "The field '" + field
                      + "' is not declared in the view. Valid fields: " + fieldsToString(names));
                }
              }
            }
          }
        }
      }

      if (isViewHandler) {
        if (!hasViewFields) {
          List<String> fields =
              new ArrayList<String>(Arrays.asList(m.getSimpleName().split("[$]")));
          int lastIndex = fields.size() - 1;
          String eventName = fields.get(lastIndex);

          // Validate event name
          if (eventType != null
              && !"com.google.gwt.event.shared.GwtEvent".equals(eventType.getQualifiedName())) {
            String simpleName = eventType.getSimpleName();
            if (!simpleName.endsWith("Event")) {
              messager.printError(eventTypeDeclaration.getPosition(),
                  "The event type does not match with the convention, it must end with 'Event'");
            }

            String currentEventName = eventClassNameToEventName(simpleName);
            if (!eventName.equals(currentEventName)) {
              messager.printError(m.getPosition(), "The method name must end with '"
                  + currentEventName + "'");
            }
          } else {
            String currentPackage = classDeclaration.getPackage().getQualifiedName() + ".event.";
            String eventClassName = eventNameToEventClassName(eventName);
            eventType = env.getTypeDeclaration(domPackage + eventClassName);
            if (eventType == null) {
              eventType = env.getTypeDeclaration(sharedPackage + eventClassName);
            }
            if (eventType == null) {
              eventType = env.getTypeDeclaration(currentPackage + eventClassName);
            }

            if (eventType == null) {
              messager.printWarning(m.getPosition(),
                  "The event cannot be found. Do you see any typo in the name? '" + eventName
                      + "'. i.e: ClickEvent -> click");
            }
          }

          fields.remove(lastIndex);
          for (String part : fields) {
            if (!names.contains(part)) {
              messager.printError(m.getPosition(), "The field '" + part
                  + "' is not declared in the view. Valid fields: " + fieldsToString(names));
              break;
            }
          }
        } else {
          if (eventType == null) {
            messager
                .printError(
                    viewHandlerDeclaration.getPosition(),
                    "When using the @ViewFields annotation you need to specify the event type on the @ViewHandler annotation");
          }
        }

        // Parameters
        Collection<ParameterDeclaration> parameters = m.getParameters();
        for (ParameterDeclaration p : parameters) {

          boolean isAttribute = false;
          Collection<AnnotationMirror> parameterAnnotations = p.getAnnotationMirrors();
          for (AnnotationMirror annotationMirror : parameterAnnotations) {
            if (annotationMirror.getAnnotationType().getDeclaration().getQualifiedName().equals(
                "com.guit.client.binder.Attribute")) {
              isAttribute = true;
              break;
            }
          }
          if (isAttribute) {
            if (p.getType() instanceof PrimitiveType) {
              messager
                  .printError(
                      p.getPosition(),
                      "All @Attribute parameters must not be of a primitive type. The type must implement valueOf(String value)");
            }
            continue;
          }

          String name = p.getSimpleName();
          String[] parts = name.split("[$]");

          HashMap<String, MethodDeclaration> validFields = getValidFields(eventType);
          StringBuilder sb = new StringBuilder();
          String lastPart = null;
          for (String part : parts) {
            if (sb.length() > 0) {
              sb.append(".");
            }

            if (lastPart != null) {
              TypeMirror returnClassType = validFields.get(part).getReturnType();
              if (returnClassType instanceof TypeDeclaration) {
                TypeDeclaration returnType = (TypeDeclaration) returnClassType;
                validFields = getValidFields(returnType);
              }
            }

            if (!validFields.keySet().contains(part)) {
              messager.printError(p.getPosition(), "The event '" + eventType.getQualifiedName()
                  + "' does not have a getter method for '" + sb.toString() + part + "'");
            }

            lastPart = part;
            sb.append(part);
          }
        }

        // TODO Check types
      }
    }
  }

  private void processController(ClassDeclaration classDeclaration) {
    Collection<MethodDeclaration> methods = classDeclaration.getMethods();
    for (MethodDeclaration m : methods) {

      Collection<AnnotationMirror> annotationsMirror = m.getAnnotationMirrors();

      TypeDeclaration eventType = null;
      AnnotationTypeElementDeclaration eventTypeDeclaration = null;
      boolean isEventBusHandler = false;
      for (AnnotationMirror mirror : annotationsMirror) {
        AnnotationTypeDeclaration declaration = mirror.getAnnotationType().getDeclaration();

        if (declaration == null) {
          continue;
        }

        // EventBusHandler
        if (declaration.getQualifiedName().equals("com.guit.client.binder.EventBusHandler")) {
          isEventBusHandler = true;
          Map<AnnotationTypeElementDeclaration, AnnotationValue> map = mirror.getElementValues();
          for (Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : map.entrySet()) {
            eventTypeDeclaration = entry.getKey();
            try {
              eventType = ((ClassType) entry.getValue().getValue()).getDeclaration();
            } catch (ClassCastException e) {
              continue;
            }
          }
          break;
        }
      }

      if (isEventBusHandler) {
        List<String> fields = new ArrayList<String>(Arrays.asList(m.getSimpleName().split("[$]")));
        if (fields.size() != 2 || (!fields.get(0).equals("eventBus") && !fields.get(0).isEmpty())) {
          messager.printError(m.getPosition(),
              "The method name must be 'eventBus${eventname}' or '${eventname}'");
          continue;
        }

        String eventName = fields.get(1);

        // Validate event name
        if (eventType != null
            && !"com.google.gwt.event.shared.GwtEvent".equals(eventType.getQualifiedName())) {
          String simpleName = eventType.getSimpleName();
          if (!simpleName.endsWith("Event")) {
            messager.printError(eventTypeDeclaration.getPosition(),
                "The event type does not match with the convention, it must end with 'Event'");
          }

          String currentEventName = eventClassNameToEventName(simpleName);
          if (!eventName.equals(currentEventName)) {
            messager.printError(m.getPosition(), "The method name must end with '"
                + currentEventName + "'");
          }
        } else {
          // String currentPackage =
          // classDeclaration.getPackage().getQualifiedName() + ".event.";
          // String eventClassName = eventNameToEventClassName(eventName);
          // eventType = env.getTypeDeclaration(domPackage + eventClassName);
          // if (eventType == null) {
          // eventType = env.getTypeDeclaration(sharedPackage + eventClassName);
          // }
          // if (eventType == null) {
          // eventType = env.getTypeDeclaration(currentPackage +
          // eventClassName);
          // }
          //
          // if (eventType == null) {
          // messager.printWarning(m.getPosition(),
          // "The event cannot be found. Do you see any typo in the name? '" +
          // eventName
          // + "'. i.e: ClickEvent -> click");
          // }
          continue;
        }

        // Parameters
        Collection<ParameterDeclaration> parameters = m.getParameters();
        for (ParameterDeclaration p : parameters) {
          Collection<AnnotationMirror> parameterAnnotations = p.getAnnotationMirrors();
          boolean hasAttribute = false;
          for (AnnotationMirror annotationMirror : parameterAnnotations) {
            if (annotationMirror.getAnnotationType().getDeclaration().getQualifiedName().equals(
                "com.guit.client.binder.Attribute")) {
              hasAttribute = true;
              break;
            }
          }
          if (hasAttribute) {
            messager.printError(p.getPosition(),
                "@Attribute parameters are not allowed in EventBusHandler methods");
            continue;
          }

          String name = p.getSimpleName();
          String[] parts = name.split("[$]");

          HashMap<String, MethodDeclaration> validFields = getValidFields(eventType);
          StringBuilder sb = new StringBuilder();
          for (String part : parts) {
            if (sb.length() > 0) {
              sb.append(".");
            }

            if (!validFields.keySet().contains(part)) {
              messager.printError(p.getPosition(), "The event '" + eventType.getQualifiedName()
                  + "' does not have a getter method for '" + sb.toString() + part + "'");
            }

            TypeDeclaration returnType =
                ((ClassType) validFields.get(part).getReturnType()).getDeclaration();
            validFields = getValidFields(returnType);
            sb.append(part);
          }
        }

        // TODO Check types
      }
    }
  }

  private void processContainerMethod(TypeDeclaration d, MethodDeclaration m) {
    // Get name
    String containerName = null;
    Collection<AnnotationMirror> mirrors = m.getAnnotationMirrors();
    for (AnnotationMirror am : mirrors) {
      if (am.getAnnotationType().getDeclaration().getQualifiedName().equals(
          GwtDisplay.class.getCanonicalName())) {
        Map<AnnotationTypeElementDeclaration, AnnotationValue> values = am.getElementValues();
        for (Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : values.entrySet()) {
          if (entry.getKey().getSimpleName().equals("value")) {
            containerName = (String) entry.getValue().getValue();
          }
        }
      }
    }

    if (containerName == null) {
      return;
    }

    // Generate annotation
    String name = containerName;
    String packageName = d.getPackage().getQualifiedName();
    String qualifiedName = packageName + "." + name;
    PrintWriter writer = getPrintWriter(qualifiedName);
    writer.println("package " + packageName + ";");
    writer.println();
    writer.println("import java.lang.annotation.Documented;");
    writer.println("import java.lang.annotation.Retention;");
    writer.println("import java.lang.annotation.RetentionPolicy;");
    writer.println();
    writer.println("import com.google.inject.BindingAnnotation;");
    Generated.printGeneratedImport(writer);
    writer.println();
    writer.println("@BindingAnnotation");
    writer.println("@Retention(RetentionPolicy.RUNTIME)");
    writer.println("@Documented");
    writer.println();
    Generated.printGenerated(writer, d.getSimpleName());
    writer.println("public @interface " + name + "{");
    writer.println("}");
    writer.close();

    // Generate provider
    name = containerName + "Provider";
    qualifiedName = packageName + "." + name;
    writer = getPrintWriter(qualifiedName);
    writer.println("package " + packageName + ";");
    writer.println();
    writer.println("import com.google.gwt.user.client.ui.AcceptsOneWidget;");
    writer.println("import com.google.gwt.user.client.ui.IsWidget;");
    writer.println("import com.google.inject.Inject;");
    writer.println("import com.google.inject.Provider;");
    writer.println("import com.google.inject.Singleton;");
    Generated.printGeneratedImport(writer);
    writer.println();
    writer.println("@Singleton");
    Generated.printGenerated(writer, d.getSimpleName());
    writer.println("public class " + name
        + " implements Provider<AcceptsOneWidget>, AcceptsOneWidget {");
    writer.println();
    writer.println("  @Inject");
    writer.println("  " + d.getQualifiedName() + " owner;");
    writer.println();
    writer.println("  @Override");
    writer.println("  public void setWidget(IsWidget w) {");
    writer.println("    owner." + m.getSimpleName() + "(w);");
    writer.println("  }");
    writer.println();
    writer.println("  @Override");
    writer.println("  public AcceptsOneWidget get() {");
    writer.println("    return this;");
    writer.println("  }");
    writer.println("}");
    writer.close();

    // Generate module
    name = containerName + "Module";
    qualifiedName = packageName + "." + name;
    writer = getPrintWriter(qualifiedName);
    writer.println("package " + packageName + ";");
    writer.println();
    writer.println("import com.google.gwt.inject.client.AbstractGinModule;");
    writer.println("import com.google.gwt.user.client.ui.AcceptsOneWidget;");
    Generated.printGeneratedImport(writer);
    writer.println();
    Generated.printGenerated(writer, d.getSimpleName());
    writer.println("public class " + name + " extends AbstractGinModule {");
    writer.println();
    writer.println("  @Override");
    writer.println("  protected void configure() {");
    writer.println("    bind(AcceptsOneWidget.class).annotatedWith(" + containerName
        + ".class).toProvider(" + containerName + "Provider.class);");
    writer.println("  }");
    writer.println("}");
    writer.close();
  }

  public PrintWriter getPrintWriter(String qualifiedName) {
    try {
      return filer.createSourceFile(qualifiedName);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
}
